﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace ObjectDirectory
{
    public class ObjectPathManager
    {
        /// <summary>
        /// Attempt to get from the root object to the leaf
        /// </summary>
        /// <param name="rootObject"></param>
        /// <param name="leaf"></param>
        /// <returns></returns>
        public string GetPath(object rootObject, object leaf)
        {
            ///
            /// Check out inputs
            /// 

            if (rootObject == null)
            {
                throw new ArgumentException("Root object can't be null when trying to find relative path");
            }

            if (leaf == null)
            {
                throw new ArgumentException("Leaf object must not be null!");
            }

            ///
            /// Simple case is if we are doing something dumb
            /// 

            if (leaf == rootObject)
            {
                return ".";
            }

            ///
            /// Recurse
            /// 

            return FindObject(rootObject, leaf);
        }

        /// <summary>
        /// Go down one level... and keep doing it.
        /// </summary>
        /// <param name="rootObject"></param>
        /// <param name="leaf"></param>
        /// <returns></returns>
        private string FindObject(object rootObject, object leaf, int depth = 0)
        {
            if (depth > 200)
            {
                throw new ArgumentException("Attempted more than " + depth.ToString() + "levels deep to find path and failed");
            }

            ///
            /// See if we get lucky and find the thing we actually need
            /// 

            foreach (var p in rootObject.GetType().GetProperties())
            {
                try
                {
                    if (leaf == p.GetValue(rootObject, null))
                    {
                        return p.Name;
                    }
                }
                catch
                {
                }
            }

            ///
            /// Ok, now we have to go down a level
            /// 

            foreach (var p in rootObject.GetType().GetProperties())
            {
                object nextlevel = null;
                try
                {
                    nextlevel = p.GetValue(rootObject, null);
                }
                catch
                {
                    continue;
                }

                if (nextlevel == null)
                    continue;

                var subPath = FindObject(nextlevel, leaf, depth + 1);
                if (subPath.Length != 0)
                {
                    return p.Name + "/" + subPath;
                }
            }

            return "";
        }

        /// <summary>
        /// Given a path see if we can't find it in our object.
        /// </summary>
        /// <param name="root"></param>
        /// <param name="relativePath"></param>
        /// <returns></returns>
        public object FindObjectFromPath(object root, string relativePath)
        {
            if (root == null)
            {
                throw new ArgumentException("Can't navagate from a null root object!");
            }

            ///
            /// Take care of the easy case - the same object.
            /// 

            if (relativePath == null
                || relativePath == ""
                || relativePath == ".")
            {
                return root;
            }

            ///
            /// Ok - done with the easy stuff. Now we can go after the real thing.
            /// 

            var paths = relativePath.Split('/');

            return TraverseObjectTree(root, paths);
        }

        /// <summary>
        /// Simple loop to go after the object.
        /// </summary>
        /// <param name="root"></param>
        /// <param name="relativePath"></param>
        /// <returns></returns>
        private object TraverseObjectTree(object root, string[] relativePaths)
        {
            ///
            /// Move down the object graph
            /// 
            foreach (var p in relativePaths)
            {
                ///
                /// Get the property
                /// 

                if (p == ""
                    || p == ".")
                {
                    continue;
                }

                var pinfo = root.GetType().GetProperty(p);
                if (pinfo == null)
                    throw new ArgumentException("Unable to find the property named " + p);

                ///
                /// Now that we have the property, next job is to update the root object
                /// with this new guy
                /// 

                root = pinfo.GetValue(root, null);
                if (root == null)
                {
                    throw new ArgumentException("Null value found when looking at " + p);
                }
            }

            return root;
        }
    }
}
